using System.Collections.Generic;
using System.Data;
using Agile.Genie.Descriptors;
using GeneratorCustomization;

namespace Descriptors.GeneratorCustomization
{
    /// <summary>
    /// Container for all tables that are included tables.
    /// We only genertate code for these tables.
    /// </summary>
    public class IncludedTable
    {
        private readonly string _database;
        private readonly string _table;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="table">Name of the table to be included.</param>
        /// <param name="database">Name of the database the table is in.</param>
        private IncludedTable(string table, string database)
        {
            _table = table;
            _database = database;
        }

        /// <summary>
        /// Name of the table that is to be included.
        /// </summary>
        public string Table
        {
            get { return _table; }
        }

        /// <summary>
        /// Name of the database that the table is in.
        /// </summary>
        public string Database
        {
            get { return _database; }
        }

        /// <summary>
        /// Instantiate a new included table.
        /// </summary>
        /// <param name="table">Name of the table to be included.</param>
        /// <returns></returns>
        public static IncludedTable Build(string table)
        {
            return new IncludedTable(table, string.Empty);
        }

        /// <summary>
        /// Instantiate a new included table.
        /// </summary>
        /// <param name="table">Name of the table to be included.</param>
        /// <param name="database">Name of the database the table is in.</param>
        /// <returns></returns>
        public static IncludedTable Build(string table, string database)
        {
            return new IncludedTable(table, database);
        }

        private readonly static Dictionary<string,List<string>> excludedTables = new Dictionary<string,List<string>>(); 

        /// <summary>
        /// Returns true if the database the table is in has an included column 'All'.
        /// OR there is a match on the database and table name in the included columns xml file, 'IncludedTable' table.
        /// Also encapsulates logic for 'EXCLUDED' tables.
        /// </summary>
        public static bool TableShouldBeIncluded(DatabaseTable table)
        {
            // if it's not set to be 'included' then return false 
            // note that for most databases, it will have 'All' tables
            if (! IncludedTableCollection.All.MatchOnDatabaseAndTable(table))
                return false;

            // Ok so if it is to be Included (could be because ALL tables are included)
            // then we need to double check that the table has not been explicitly excluded.
            PopulateExcludedTableDictionary();
            
            // first check the tables excluded from ALL database code generation
            
            if(excludedTables.ContainsKey("All"))
            {
                if (IsTableExcluded(table, excludedTables["All"]))
                    return false;
            }
            // now check the tables excplicitly excluded for this database
            if(excludedTables.ContainsKey(table.DatabaseName))
            {
                if (IsTableExcluded(table, excludedTables[table.DatabaseName]))
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Returns true if the table has been defined to be excluded (in GeneratorDetailsData)
        /// </summary>
        private static bool IsTableExcluded(DatabaseTable table, List<string> excludedList)
        {
                foreach(string excluded in excludedList)
                {
                    // first, we allow ending with *
                    if(excluded.EndsWith("*"))
                    {
                        if(table.Name.StartsWith(excluded.Replace("*", ""), System.StringComparison.InvariantCultureIgnoreCase))
                            return true;
                    }
                    
                        // then, we allow starting with *
                    else if(excluded.StartsWith("*"))
                    {
                        if(table.Name.EndsWith(excluded.Replace("*", ""), System.StringComparison.InvariantCultureIgnoreCase))
                            return true;
                    }

                        // finally check the exact match
                    else if(excluded.Equals(table.Name, System.StringComparison.InvariantCultureIgnoreCase))
                        return true;
                }
            
            return false;
        }

        private static void PopulateExcludedTableDictionary()
        {
            // only populate once
            if(excludedTables.Count != 0)
                return;

            foreach (DataRow row in GeneratorsData.GetDataRowsFor("ExcludedTable"))
            {
                string database = row["Database"].ToString();
                string excludedText = row["TableName"].ToString();

                // first see if there is an existing list for the database
                
                if(excludedTables.ContainsKey(database))
                {
                    List<string> databaseList = excludedTables[database];
                    databaseList.Add(excludedText);
                }
                else
                {
                    List<string> databaseList = new List<string>();
                    databaseList.Add(excludedText);
                    excludedTables.Add(database, databaseList);
                }
            }
        }

        #region Nested type: Testing

        /// <summary>
        /// Support methods for testing use only
        /// </summary>
        public class Testing
        {
            /// <summary>
            /// Get the test included table. Details are:-
            /// TestTable
            /// </summary>
            /// <returns></returns>
            public static IncludedTable GetTestIncludedTable()
            {
                return Build("TestTable");
            }
        }

        #endregion
    }

}